package com.wave.system.util;

import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Modbus4jUtils {
    private static final Log log = LogFactory.getLog(Modbus4jUtils.class);

    public static ModbusMaster tcpMaster = null;

    static {
        try {
            getMaster();
        } catch (ModbusInitException e) {
            throw new RuntimeException(e);
        }
    }

    public static ModbusMaster getMaster() throws ModbusInitException {
        if (tcpMaster == null) {
            ModbusFactory modbusFactory = new ModbusFactory();
            IpParameters params = new IpParameters();
            params.setHost("192.168.150.10");
            params.setPort(502);
            tcpMaster = modbusFactory.createTcpMaster(params, true);
            tcpMaster.init();
        }
        return tcpMaster;

    }

    /**
     * 写 [01 Coil Status(0x)]写一个 function ID = 5
     *
     * @param slaveId     slave的ID
     * @param writeOffset 位置
     * @param writeValue  值
     */
    public static boolean writeCoil(int slaveId, int writeOffset, boolean writeValue) {
        try {
            // 获取master
            ModbusMaster tcpMaster = getMaster();
            // 创建请求
            WriteCoilRequest request = new WriteCoilRequest(slaveId, writeOffset, writeValue);
            // 发送请求并获取响应对象
            WriteCoilResponse response = (WriteCoilResponse) tcpMaster.send(request);
            return !response.isException();
        } catch (Exception e) {
            log.error("write coil failed", e);
        }
        return false;
    }

    /**
     * 写[01 Coil Status(0x)] 写多个 function ID = 15
     *
     * @param slaveId
     * @param startOffset 开始位置
     * @param bdata       写入的数据
     * @return 是否写入成功
     */
    public static boolean writeCoils(int slaveId, int startOffset, boolean[] bdata)
        throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster();
        // 创建请求
        WriteCoilsRequest request = new WriteCoilsRequest(slaveId, startOffset, bdata);
        // 发送请求并获取响应对象
        WriteCoilsResponse response = (WriteCoilsResponse) tcpMaster.send(request);
        if (response.isException()) {
            return false;
        } else {
            return true;
        }

    }

    /***
     * 写[03 Holding Register(4x)] 写一个 function ID = 6
     * @param slaveId
     * @param writeOffset
     * @param writeValue
     */
    public static boolean writeRegister(int slaveId, int writeOffset, short writeValue)
        throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster();
        // 创建请求对象
        WriteRegisterRequest request = new WriteRegisterRequest(slaveId, writeOffset, writeValue);
        WriteRegisterResponse response = (WriteRegisterResponse) tcpMaster.send(request);
        if (response.isException()) {
            log.error(response.getExceptionMessage());
            return false;
        } else {
            return true;
        }

    }

    /**
     * 写入[03 Holding Register(4x)]写多个 function ID=16
     *
     * @param slaveId
     * @param startOffset 起始位置偏移量值
     * @param sdata       写入的数据
     */
    public static boolean writeRegisters(int slaveId, int startOffset, short[] sdata)
        throws ModbusTransportException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster();
        // 创建请求对象
        WriteRegistersRequest request = new WriteRegistersRequest(slaveId, startOffset, sdata);
        // 发送请求并获取响应对象
        ModbusResponse response = tcpMaster.send(request);
        if (response.isException()) {
            log.error(response.getExceptionMessage());
            return false;
        } else {
            return true;
        }
    }

    /**
     * 写入数字类型的模拟量（如:写入Float类型的模拟量、Double类型模拟量、整数类型Short、Integer、Long）
     *
     * @param slaveId
     * @param offset
     * @param value    写入值,Number的子类,例如写入Float浮点类型,Double双精度类型,以及整型short,int,long
     * @param dataType com.serotonin.modbus4j.code.DataType
     */
    public static void writeHoldingRegister(int slaveId, int offset, Number value, int dataType)
        throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 获取master
        ModbusMaster tcpMaster = getMaster();
        // 类型
        BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, dataType);
        tcpMaster.setValue(locator, value);
    }

    /**
     * 工具类方法 setString（）主要进行了一下数据类型转换
     *
     * @param value
     * @return
     */
    public static short[] setString(String value) {
        byte[] bytesTemp = value.getBytes(StandardCharsets.UTF_8);
        byte[] bytes;
        if (bytesTemp.length % 2 > 0) {
            bytes = Arrays.copyOf(bytesTemp, bytesTemp.length + 1);
        } else {
            bytes = bytesTemp;
        }
        return bytesToShort(bytes);
    }


    /***
     *  读取[01 Coil Status 0x]类型 开关数据
     * @author zhengfuping
     * @param slaveId 从站编号
     * @param offset 偏移位置
     * @return Boolean
     */
    public static Boolean readCoilStatus(int slaveId, int offset) {
        try {
            BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
            Boolean value = getMaster().getValue(loc);
            return value;
        } catch (Exception e) {
            log.error("read coil status failed", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取多个线圈状态
     *
     * @param slaveId      从站地址（1-247）
     * @param startAddress 起始地址（对应协议地址00001的地址值为0）
     * @param quantity     读取数量（最大2000）
     * @throws ModbusTransportException 通信异常
     */
    public static boolean[] batchReadCoilStatus(int slaveId, int startAddress, int quantity) {
        try {
            ReadCoilsRequest readCoilsRequest = new ReadCoilsRequest(slaveId, startAddress, quantity);
            ReadCoilsResponse response = (ReadCoilsResponse) getMaster().send(readCoilsRequest);
            if (!response.isException()) {
                boolean[] rawData = response.getBooleanData();
                return Arrays.copyOfRange(rawData, 0, quantity);
            }
            throw new RuntimeException("读取多个线圈数据失败");
        } catch (Exception e) {
            log.error("batch read coil status failed", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 读取[03 Holding Register类型 2x]模拟量数据
     *
     * @param slaveId
     * @param offset   位置
     * @param dataType 数据类型,来自com.serotonin.modbus4j.code.DataType
     */
    public static Number readHoldingRegister(int slaveId, int offset, int dataType)
        throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
        Number value = getMaster().getValue(loc);
        return value;
    }

    /**
     * Byte数组转short数组
     *
     * @param bytes
     * @return
     */
    public static short[] bytesToShort(byte[] bytes) {
        if (bytes == null) {
            return null;
        }
        short[] shorts = new short[bytes.length / 2];
        ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shorts);
        return shorts;

    }


    /**
     * 工具类方法getString()  类型转换
     *
     * @param src
     * @param start
     * @param len
     * @return
     */
    private static String getString(short[] src, int start, int len) {
        short[] temp = new short[len];
        for (int i = 0; i < len; i++) {
            temp[i] = src[i + start];
        }
        byte[] bytesTemp = shortsToBytes(temp);
        return new String(bytesTemp, StandardCharsets.UTF_8);
    }

    /**
     * short数组转byte
     *
     * @param data
     * @return
     */
    private static byte[] shortsToBytes(short[] data) {
        byte[] byteValue = new byte[data.length * 2];
        for (int i = 0; i < data.length; i++) {
            byteValue[i * 2] = (byte) (data[i] & 0xff);
            byteValue[i * 2 + 1] = (byte) ((data[i] & 0xff00) >> 8);
        }
        return byteValue;
    }

    /**
     * 读保持寄存器上的内容
     *
     * @param slaveId 从站地址
     * @param start   起始地址的偏移量
     * @param len     待读寄存器的个数
     */

    public static String readHoldingRegistersToString(int slaveId, int start, int len) {
        String s = "";
        try {
            ModbusMaster master = getMaster();
            ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest(slaveId, start, len);
            ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) master.send(request);
            if (response.isException()) {
                System.out.println("Exception response: message=" + response.getExceptionMessage());
            } else {
                s = getString(response.getShortData(), 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return s;
    }
}
